﻿module Sustainalytics.LRTM {

    import Common = Sustainalytics.Common;

    // task manager service - client side implementation for task manager
    export class LongRunningTaskManagerService implements ILongRunningTaskManagerService {

        // dictionary of task instances ordered by key
        private _taskInstances: Common.Dictionary<LongRunningTaskInstance>;

        // dictionary of task instances ordered by order
        private _taskInstancesOrdered: Common.Dictionary<LongRunningTaskInstance>;

        // task manager settings
        private _settings: LongRunningTaskManagerSettingsDto;


        // ctor
        constructor(
            private $rootScope: ng.IScope,
            private $timeout: ng.ITimeoutService,
            private $q: ng.IQService,
            private LongRunningTaskService: LongRunningTaskService,
            private notificationFactory: Common.INotificationFactory,
            private LongRunningTaskManagerSettingsService: LongRunningTaskManagerSettingsService) {

            var ctx = this;

            // init dictionary
            ctx._taskInstances = new Common.Dictionary<LongRunningTaskInstance>();

            // init list of task instances
            ctx._taskInstancesOrdered = new Common.Dictionary<LongRunningTaskInstance>();            

            // init
            ctx.init();
        }

        // init
        private init(): void {
            var ctx = this;

            // get settings
            ctx.LongRunningTaskManagerSettingsService.get().success(function (settings) {
                ctx._settings = settings;
            });

            // init SignalR for obtaining real time task instance statuses
            ctx.$rootScope.$watch('authData', function () {
                ctx.initSignalR();
            });
        }

        // init SignalR
        private initSignalR(): void {
            var ctx = this;

            // get auth token
            var authToken = ctx.$rootScope['authData'] && ctx.$rootScope['authData']['access_token'] ? ctx.$rootScope['authData']['access_token'] : null;

            if (authToken) {
                // LRTM hub
                var lrtmHub = $.connection['longRunningTaskHub'];

                // set access token
                lrtmHub.connection.qs = { 'access_token': authToken };

                // receive task statuses from server
                lrtmHub['client']['sendTaskStatusesToUser'] = function (statuses: LongRunningTaskInstanceStatus[]) {

                    // sync received statuses with current statuses
                    ctx.syncTaskStatuses(statuses);
                }

                lrtmHub.connection.url = "/lrtmserv/signalr";
                // when done connecting to SignalR LRTM hub
                lrtmHub.connection.start();
            }
        }


        // sync task statuses with current task statuses
        public syncTaskStatuses(statuses: LongRunningTaskInstanceStatus[]) {
            var ctx = this;

            ctx.$timeout(() => {
                this.$rootScope.$apply(() => {
                    // create dictionary of statuses
                    var reqStatusDic = new Common.Dictionary<LongRunningTaskInstanceStatus>();
                    $.each(statuses, (index, status) => {
                        reqStatusDic.add(status.id, status);
                    });

                    // remove non existing task instances except the virtual ones
                    var currentTaskInstances = this._taskInstances.getValues();
                    $.each(currentTaskInstances, (index, taskInstance) => {
                        if (!taskInstance.isVirtual && reqStatusDic.containsKey(taskInstance.status.id) == false) {
                            this._taskInstances.removeKey(taskInstance.status.id);
                            this._taskInstancesOrdered.removeKey(taskInstance.status.order.toString());
                        }
                    });

                    // update or add new task instances
                    $.each(statuses, (index, status) => {
                        var taskInstance: LongRunningTaskInstance;
                        if (this._taskInstances.containsKey(status.id)) {
                            // update task status
                            taskInstance = this._taskInstances.getValue(status.id);
                            taskInstance.setStatus(status.taskInstanceStatus);
                            taskInstance.status = status;
                            taskInstance.isVirtual = false;
                            taskInstance.status.isUpload = false;
                        }
                        else {
                            // create new task instance
                            taskInstance = LongRunningTaskInstance.createFromServer(status);
                            this._taskInstances.add(taskInstance.status.id, taskInstance);
                            this._taskInstancesOrdered.add(taskInstance.status.order.toString(), taskInstance);
                        }
                    });
                });
            });
        }

        // get task instances
        public getTaskInstances(): LongRunningTaskInstance[]{

            var instances = this._taskInstancesOrdered.getValues();
            return instances;
        }

        // get task instance
        public getTaskInstance(taskInstanceId: string): LongRunningTaskInstance {

            var instance = this._taskInstances.getValue(taskInstanceId);
            return instance;
        }

        // run task
        // returns taskInstanceId
        public runTask(
            title: string,
            paramJson: string,
            taskType: LongRunningTaskTypeEnum,
            isDownload: boolean,
            statusChangedCallback: (taskInstanceId: string, newStatus: LongRunningTaskInstanceStatusEnum, oldStatus: LongRunningTaskInstanceStatusEnum, taskInstance: LongRunningTaskInstance) => void): ng.IPromise<string> {

            var ctx = this;

            var postInfo: LongRunningTaskInstancePost = ctx.addVirtualTaskInstance(title, taskType, false, isDownload,
                (virtualTaskInstance) => {
                    var postInfo = this.LongRunningTaskService.postTask(virtualTaskInstance.status.id, title, paramJson, taskType, isDownload, virtualTaskInstance.status.order);
                    return postInfo;

                }, statusChangedCallback);            

            return postInfo.promise;
        }

        // run task + upload
        // returns taskInstanceId
        public runTaskWithUpload(
            title: string,
            paramJson: string,
            taskType: LongRunningTaskTypeEnum,
            files: any[],
            uploadProgressFunc: (number) => void,
            isDownload: boolean,
            statusChangedCallback: (taskInstanceId: string, newStatus: LongRunningTaskInstanceStatusEnum, oldStatus: LongRunningTaskInstanceStatusEnum, taskInstance: LongRunningTaskInstance) => void): ng.IPromise<string> {

            var ctx = this;

            var postInfo: LongRunningTaskInstancePost = ctx.addVirtualTaskInstance(title, taskType, true, isDownload, (virtualTaskInstance) => {

                // request task + upload
                var postInfo = this.LongRunningTaskService.postTaskWithUpload(virtualTaskInstance.status.id, title, paramJson, taskType, files,
                    // upload progress
                    (progress: number) => {

                        // update virtual task instance progress
                        virtualTaskInstance.status.taskStatus.progress = progress;

                        //if (progress == 100) {
                        //    virtualTaskInstance.setStatus(LongRunningTaskInstanceStatusEnum.Finished);
                        //}

                        // if a user defined progress func is defined then pass the progress
                        if (uploadProgressFunc) {
                            uploadProgressFunc(progress);
                        }
                    },
                    isDownload, virtualTaskInstance.status.order);

                return postInfo;

            }, statusChangedCallback);

            return postInfo.promise;
        }

        // create a new virtual task instance and manages it for the duration of its lifetime as long as is being marked as virtual

        public addAlreadyCreateTaskInstance(task: LongRunningTaskInstance) {
            var ctx = this;
            ctx._taskInstances.add(task.status.id, task);
            ctx._taskInstancesOrdered.add(task.status.order.toString(), task);
        }

        public generateUUID()
        {
            var d = new Date().getTime();
            var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = (d + Math.random() * 16) % 16 | 0;
                d = Math.floor(d / 16);
                return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
            });
            return uuid;
        }

        private addVirtualTaskInstance(
            title: string,
            taskType: LongRunningTaskTypeEnum,
            isUpload: boolean,
            isDownload: boolean,
            createTaskInstanceFunc: (virtualTaskInstance: LongRunningTaskInstance) => LongRunningTaskInstancePost,
            statusChangedCallback: (taskInstanceId: string, newStatus: LongRunningTaskInstanceStatusEnum, oldStatus: LongRunningTaskInstanceStatusEnum, taskInstance: LongRunningTaskInstance) => void): LongRunningTaskInstancePost {

            var ctx = this;

            // determine if the max number of tasks was reached
            if (ctx._settings && ctx._settings.maxTasksPerUser && ctx._taskInstances.getValues().length >= ctx._settings.maxTasksPerUser) {
                ctx.displayMaxTasksReachedError();
                return;
            }

            var taskInstances = ctx.getTaskInstances();
            var lastOrder = taskInstances.length > 0 ? taskInstances[taskInstances.length - 1].status.order : 0;

            // create virtual task
            //new Date().getMilliseconds().toString()
            var virtualTaskInstance = LongRunningTaskInstance.createVirtual(ctx.generateUUID(), title, taskType, lastOrder + 1, true, isDownload);

            // add task instance
            ctx._taskInstances.add(virtualTaskInstance.status.id, virtualTaskInstance);
            ctx._taskInstancesOrdered.add(virtualTaskInstance.status.order.toString(), virtualTaskInstance);

            // create task instance
            var postInfo: LongRunningTaskInstancePost = createTaskInstanceFunc(virtualTaskInstance);

            // set post info to task instance
            virtualTaskInstance.post = postInfo;
            virtualTaskInstance.statusChangedCallback = statusChangedCallback;


            postInfo.promise
                // when task was created then set virtual task instance ID
                .then(function (res: ng.IHttpPromiseCallbackArg<any>) {
                    if (!res || !res.data)
                        return;

                    // get task instance id
                    var taskInstanceId = res.data;

                    // update only if virtual
                    if (virtualTaskInstance.isVirtual) {

                        // remove virtual
                        ctx._taskInstances.removeKey(virtualTaskInstance.status.id);
                        ctx._taskInstancesOrdered.removeKey(virtualTaskInstance.status.order.toString());

                        // add task only if the new id does not exist
                        if (ctx._taskInstances.containsKey(taskInstanceId) == false) {
                            virtualTaskInstance.status.id = taskInstanceId;
                            ctx._taskInstances.add(virtualTaskInstance.status.id, virtualTaskInstance);
                            ctx._taskInstancesOrdered.add(virtualTaskInstance.status.order.toString(), virtualTaskInstance);
                        }
                    }
                })
                // if task failed to be created then remove it from list
                ['catch'](function (err) {
                    if (err) {
                        var errCode = JSON.parse(err)['code'];

                        // Max ammount of user reached
                        if (errCode == 1) {
                            ctx.displayMaxTasksReachedError();

                            // remove task instance
                            ctx._taskInstances.removeKey(virtualTaskInstance.status.id);
                            ctx._taskInstancesOrdered.removeKey(virtualTaskInstance.status.order.toString());
                            return;
                        }
                    }
                    

                    if (virtualTaskInstance.status.isUpload) {
                        virtualTaskInstance.setStatus(LongRunningTaskInstanceStatusEnum.Failed);
                    }
                    else {
                        if (virtualTaskInstance.isVirtual) {
                            ctx._taskInstances.removeKey(virtualTaskInstance.status.id);
                            ctx._taskInstancesOrdered.removeKey(virtualTaskInstance.status.order.toString());
                        }
                    }
                });


            return postInfo;
        }

        private displayMaxTasksReachedError(): void {
            var ctx = this;
            var msg = 'You have exceeded the maximum number of simultaneous operations. Please finalize one of the items below before initiating a new operation.';
            ctx.notificationFactory.error('', msg);
        }


        // delete specific task
        public deleteTask(taskInstanceId: string): ng.IPromise<any> {

            var ctx = this;

            // get task instance
            var taskInstance = ctx._taskInstances.getValue(taskInstanceId);

            // stop upload
            if (taskInstance.status.taskInstanceStatus == LongRunningTaskInstanceStatusEnum.Running) {
                taskInstance.post.abort();
                taskInstance.setStatus(LongRunningTaskInstanceStatusEnum.Cancelled);
            }

            // remove task instance
            var promise = this.LongRunningTaskService.deleteTaskInstance(taskInstanceId);

            promise['finally'](() => {
                if (taskInstance) {
                    // remove task instance
                    this._taskInstances.removeKey(taskInstanceId);

                    // remove task instance by order
                    this._taskInstancesOrdered.removeKey(taskInstance.status.order.toString());
                }
            });
            
            return promise;
        }

        // get result
        public getResult(taskInstanceId: string, deleteOnGetResult): ng.IPromise<any> {
            var promise = this.LongRunningTaskService.getResultAsObject(taskInstanceId, deleteOnGetResult);
            return promise;
        }

        // download result
        public downloadResult(taskInstanceId): void {
            var ctx = this;
            ctx.LongRunningTaskService.getResultAsDownload(taskInstanceId, true);
        }
    }


    // register service
    angular.module('Sustainalytics.LRTM').service('LongRunningTaskManagerService',
        ['$rootScope', '$timeout', '$q', 'LongRunningTaskService', 'notificationFactory', 'LongRunningTaskManagerSettingsService', LongRunningTaskManagerService]);
} 